package com.yangrd.airship.upms.support;

import com.yangrd.airship.common.spring.SpringContextHolder;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.security.RolesAllowed;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * description: RoleAuthorizeHelper
 * 目前  只支持 如 @RolesAllowed('role') 这种形式
 *
 * @author: yangrd
 * @date: 2021/6/24
 * @version: 1.0
 */
public class RoleAuthorizeHelper {

    static final String ROOT = "ROOT";

    static Collection<AuthorizeDefine> authorizeDefineCollection;

    public static Collection<AuthorizeDefine> listAuthorizeDefines(String basePackage){
        if (Objects.isNull(authorizeDefineCollection)) {
            authorizeDefineCollection = listAuthorizeDefines0(basePackage);
        }
        return authorizeDefineCollection;
    }

    private static Collection<AuthorizeDefine> listAuthorizeDefines0(String basePackage) {
        Map<String, Object> restBeans = SpringContextHolder.getApplicationContext().getBeansWithAnnotation(RestController.class);

        Collection<Object> beans = restBeans.values().stream().filter(bean -> bean.getClass().getName().contains(basePackage)).map(RoleAuthorizeHelper::getTarget).collect(Collectors.toList());
        Map<String, AuthorizeDefine> authorizeDefineMap = new HashMap<>();
        authorizeDefineMap.put(ROOT, AuthorizeDefine.of(
                ROOT,
                null,
                null,
                new HashSet<>()
        ));
        for (Object bean : beans) {
            List<Method> methods = MethodUtils.getMethodsListWithAnnotation(bean.getClass(), RolesAllowed.class);
            registerAuthorizeDescribe(authorizeDefineMap, bean.getClass(), methods);

            // List<Method> methods0 = MethodUtils.getMethodsListWithAnnotation(bean.getClass(), PreAuthorize.class);
            //TODO
        }
        AuthorizeDefine authorizeDefine = authorizeDefineMap.get(ROOT);

        return authorizeDefine.getChildren();
    }

    private static void registerAuthorizeDescribe(Map<String, AuthorizeDefine> authorizeDefineMap, Class<?> clazz, List<Method> methods) {
        methods.forEach(method -> {
            RolesAllowed rolesAllowed = AnnotationUtils.getAnnotation(method, RolesAllowed.class);
            String authority = Optional.ofNullable(rolesAllowed).map(RolesAllowed::value).map(a -> a[0]).orElse("");
            registerAuthorizeDescribe(authorizeDefineMap, clazz, method, authority);
        });
    }

    private static void registerAuthorizeDescribe(Map<String, AuthorizeDefine> authorizeDefineMap, Class<?> clazz, Method method, String authority) {
        AuthorizeDescribe describe = AnnotationUtils.getAnnotation(method, AuthorizeDescribe.class);
        if (Objects.isNull(describe)) {
            describe = AnnotationUtils.getAnnotation(clazz, AuthorizeDescribe.class);
        }
        if (Objects.nonNull(describe)) {
            RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);

            String operation = Strings.isNotBlank(describe.operation()) ? describe.operation() : requestMapping.method()[0].name();

            String[] menus = describe.value();
            for (int i = 0; i < menus.length; i++) {
                String menuName = menus[i];
                boolean isLeaf = i == menus.length - 1;
                String pMenuName = i > 0 ? menus[i - 1] : ROOT;
                AuthorizeDefine define = authorizeDefineMap.computeIfAbsent(isLeaf ? pMenuName + menuName : menuName, k -> AuthorizeDefine.of(
                        menuName,
                        isLeaf ? operation : null,
                        isLeaf ? authority : null,
                        new HashSet<>()
                ));
                if (Strings.isNotBlank(pMenuName)) {
                    authorizeDefineMap.get(pMenuName).addChildren(define);
                }
            }
        }

    }


    public static Object getTarget(Object beanInstance) {
        if (!AopUtils.isAopProxy(beanInstance)) {
            return beanInstance;
        } else if (AopUtils.isCglibProxy(beanInstance)) {
            try {
                Field h = beanInstance.getClass().getDeclaredField("CGLIB$CALLBACK_0");
                h.setAccessible(true);
                Object dynamicAdvisedInterceptor = h.get(beanInstance);

                Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
                advised.setAccessible(true);

                Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
                return target;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
